/**************************************************************************************************
 * light library                                                                                  *
 * Copyright © 2012 David Kretzmer                                                                *
 *                                                                                                *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software  *
 * and associated documentation files (the "Software"), to deal in the Software without           *
 * restriction,including without limitation the rights to use, copy, modify, merge, publish,      *
 * distribute,sublicense, and/or sell copies of the Software, and to permit persons to whom the   *
 * Software is furnished to do so, subject to the following conditions:                           *
 *                                                                                                *
 * The above copyright notice and this permission notice shall be included in all copies or       *
 * substantial portions of the Software.                                                          *
 *                                                                                                *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING  *
 * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND     *
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, *
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.        *
 *                                                                                                *
 *************************************************************************************************/

#pragma once

#include <light/light.hpp>


namespace light
{
namespace utility
{
	struct last_element {};


	//=============================================================================================
	//
	//=============================================================================================
	template<typename ...Types>
	struct type_set;

	template<typename Last>
	struct type_set<Last>
	{
		typedef Last value_type;
		typedef last_element next;

		value_type m_value;
	};

	template<typename Top, typename ...Types>
	struct type_set<Top, Types...>
	{
		typedef Top value_type;
		typedef type_set<Types...> next;

		value_type m_value;
		next m_next;
	};


	//-----------------------------------------------------------------------------
	//
	//-----------------------------------------------------------------------------
	namespace internal_type_set
	{
		template<typename Wanted, typename ...Types>
		Wanted& get(type_set<Types...> &set, std::true_type /* is wanted element */)
		{
			return set.m_value;
		}

		template<typename Wanted, typename Type>
		Wanted& get(type_set<Type> &set, std::false_type /* is not wanted element */)
		{
			static_assert(!sizeof(Wanted), "Type not found in set");
		}

		template<typename Wanted, typename ...Types>
		Wanted& get(type_set<Types...> &set, std::false_type /* is not wanted element */)
		{
			return get<Wanted>(
				set.m_next,
				typename std::is_same<Wanted, typename type_set<Types...>::next::value_type>::type()
			);
		}



		template<typename Wanted, typename ...Types>
		Wanted const& get_const(type_set<Types...> const &set, std::true_type /* is wanted element */)
		{
			return set.m_value;
		}

		template<typename Wanted, typename Type>
		Wanted const& get_const(type_set<Type> const &set, std::false_type /* is not wanted element */)
		{
			static_assert(!sizeof(Wanted), "Type not found in set");
		}

		template<typename Wanted, typename ...Types>
		Wanted const& get_const(type_set<Types...> const &set, std::false_type /* is not wanted element */)
		{
			return get_const<Wanted>(
				set.m_next,
				typename std::is_same<Wanted, typename type_set<Types...>::next::value_type>::type()
			);
		}
	}

	template<typename Wanted, typename ...Types>
	Wanted& get(type_set<Types...> &set)
	{
		return internal_type_set::get<Wanted>(
			set,
			typename std::is_same<Wanted, typename type_set<Types...>::value_type>::type()
		);
	}

	template<typename Wanted, typename ...Types>
	Wanted const& get_const(type_set<Types...> const &set)
	{
		return internal_type_set::get_const<Wanted>(
			set,
			typename std::is_same<Wanted, typename type_set<Types...>::value_type>::type()
		);
	}


	//-----------------------------------------------------------------------------
	//
	//-----------------------------------------------------------------------------
	template<typename Func, typename Type>
	void for_each(type_set<Type> &set, Func f)
	{
		f(set.m_value);
	}

	template<typename Func, typename ...Types>
	void for_each(type_set<Types...> &set, Func f)
	{
		f(set.m_value);
		for_each(set.m_next, f);
	}

	//-----------------------------------------------------------------------------
	//
	//-----------------------------------------------------------------------------
	template<typename Func, typename Type, typename Arg>
	void for_each_arg(type_set<Type> &set, Func f, Arg const &arg)
	{
		f(set.m_value, arg);
	}

	template<typename TypeSet, typename Func, typename Arg, typename ...Args>
	void for_each_arg(TypeSet &set, Func f, Arg const &arg, Args const &...args)
	{
		f(set.m_value, arg);
		for_each_arg(set.m_next, f, args...);
	}


} // namespace: utility

	using utility::last_element;
	using utility::type_set;
	using utility::for_each;
	using utility::get;
	using utility::for_each_arg;

} // namespace: light
